package edu.princeton.cs.algs4;

/*************************************************************************
 * Compilation: javac Vector.java Execution: java Vector
 * 
 * Implementation of a vector of real numbers.
 * 
 * This class is implemented to be immutable: once the client program initialize
 * a Vector, it cannot change any of its fields (N or data[i]) either directly
 * or indirectly. Immutability is a very desirable feature of a data type.
 * 
 * % java Vector x = [ 1.0 2.0 3.0 4.0 ] y = [ 5.0 2.0 4.0 1.0 ] z = [ 6.0 4.0
 * 7.0 5.0 ] 10z = [ 60.0 40.0 70.0 50.0 ] |x| = 5.477225575051661 <x, y> = 25.0
 * 
 * 
 * Note that Vector is also the name of an unrelated Java library class.
 * 
 *************************************************************************/

public class Vector {

	private int N; // length of the vector
	private double[] data; // array of vector's components

	// create the zero vector of length n
	public Vector(int n) {
		N = n;
		data = new double[N];
	}

	// create a vector from either an array or a vararg list
	public Vector(double[] d) {
		N = d.length;

		// defensive copy so that client can't alter our copy of data[]
		data = new double[N];
		for (int i = 0; i < N; i++)
			data[i] = d[i];
	}

	// create a vector from either an array or a vararg list
	// this constructor uses Java's vararg syntax to support
	// a constructor that takes a variable number of arguments, such as
	// Vector x = new Vector(1.0, 2.0, 3.0, 4.0);
	// Vector y = new Vector(5.0, 2.0, 4.0, 1.0);
	/*
	 * public Vector(double... d) { N = d.length;
	 * 
	 * // defensive copy so that client can't alter our copy of data[] data =
	 * new double[N]; for (int i = 0; i < N; i++) data[i] = d[i]; }
	 */
	// return the length of the vector
	public int length() {
		return N;
	}

	// return the inner product of this Vector a and b
	public double dot(Vector that) {
		if (this.N != that.N)
			throw new RuntimeException("Dimensions don't agree");
		double sum = 0.0;
		for (int i = 0; i < N; i++)
			sum = sum + (this.data[i] * that.data[i]);
		return sum;
	}

	// return the Euclidean norm of this Vector
	public double magnitude() {
		return Math.sqrt(this.dot(this));
	}

	// return the Euclidean distance between this and that
	public double distanceTo(Vector that) {
		if (this.N != that.N)
			throw new RuntimeException("Dimensions don't agree");
		return this.minus(that).magnitude();
	}

	// return this + that
	public Vector plus(Vector that) {
		if (this.N != that.N)
			throw new RuntimeException("Dimensions don't agree");
		Vector c = new Vector(N);
		for (int i = 0; i < N; i++)
			c.data[i] = this.data[i] + that.data[i];
		return c;
	}

	// return this + that
	public Vector minus(Vector that) {
		if (this.N != that.N)
			throw new RuntimeException("Dimensions don't agree");
		Vector c = new Vector(N);
		for (int i = 0; i < N; i++)
			c.data[i] = this.data[i] - that.data[i];
		return c;
	}

	// return the corresponding coordinate
	public double cartesian(int i) {
		return data[i];
	}

	// create and return a new object whose value is (this * factor)
	public Vector times(double factor) {
		Vector c = new Vector(N);
		for (int i = 0; i < N; i++)
			c.data[i] = factor * data[i];
		return c;
	}

	// return the corresponding unit vector
	public Vector direction() {
		if (this.magnitude() == 0.0)
			throw new RuntimeException("Zero-vector has no direction");
		return this.times(1.0 / this.magnitude());
	}

	// return a string representation of the vector
	public String toString() {
		String s = "";
		for (int i = 0; i < N; i++)
			s = s + data[i] + " ";
		return s;
	}

	// test client
	public static void main(String[] args) {
		double[] xdata = { 1.0, 2.0, 3.0, 4.0 };
		double[] ydata = { 5.0, 2.0, 4.0, 1.0 };
		Vector x = new Vector(xdata);
		Vector y = new Vector(ydata);

		System.out.println("   x       = " + x);
		System.out.println("   y       = " + y);

		Vector z = x.plus(y);
		System.out.println("   z       = " + z);

		z = z.times(10.0);
		System.out.println(" 10z       = " + z);

		System.out.println("  |x|      = " + x.magnitude());
		System.out.println(" <x, y>    = " + x.dot(y));
		System.out.println("dist(x, y) = " + x.distanceTo(y));
		System.out.println("dir(x)     = " + x.direction());

	}
}
